﻿using System;
using System.Collections.Generic;
using System.Text;
using net.veekee.common.data.csv;
using net.veekee.common.data;
using net.veekee.common.rules;
using net.veekee.common;
using net.veekee.logprocessor.plugin.context;
using net.veekee.logprocessor.plugin.output;

namespace net.veekee.logprocessor.plugin.input
{
    public delegate void Action<R, S, T>(R one, S two, T three);

    public abstract class AProcessor<E> : AParser where E : FileEntry, new ()
    {
        protected static RuleManager<ProcessorContext, E> manager = null;
        static AProcessor()
        {
            AProcessor<E>.manager = new RuleManager<ProcessorContext, E>();
            AProcessor<E>.manager.LoadRulesFromDirectory("rules");
        }

        protected abstract bool ProcessFile(String archive, String filename);

        private Dictionary<long, AWriter> writerPool = null;
        private int outputBuffer = 99999;
        private int outputBufferTrigger = 50000;
        private bool processDiscardedEntries = false;

        protected long stats_byteread = 0;
        protected long stats_bytetoread = 0;
        protected ulong stats_read = 0;
        protected ulong stats_discarded = 0;
        private ulong stats_processed = 0;
        private ulong stats_rejected = 0;
        private ulong stats_validated = 0;
        private ulong stats_prevalidated = 0;

        public AProcessor()
        {
            if (Options.GetInstance().Get("buffer.output") != null)
                Int32.TryParse(Options.GetInstance().Get("buffer.output"), out this.outputBuffer);
            Boolean.TryParse(Options.GetInstance().Get("rules.processdiscarded"), out this.processDiscardedEntries);
            if (Options.GetInstance().Get("buffer.output.beforeflush") != null)
                Int32.TryParse(Options.GetInstance().Get("buffer.output.beforeflush"), out this.outputBufferTrigger);
            else
                this.outputBufferTrigger = this.outputBuffer / 2;
            if (this.outputBufferTrigger > this.outputBuffer)
            {
                Log.GetInstance().Error("Incorrect buffer.output.beforeflush size ({0}), greater than buffer.output ({1})", this.outputBufferTrigger, this.outputBuffer);
                this.outputBufferTrigger = this.outputBuffer / 2;
            }
            this.writerPool = new Dictionary<long, AWriter>();
        }

        protected override void ReinitStats()
        {
            base.ReinitStats();
            this.stats_read = 0;
            this.stats_discarded = 0;
            this.stats_processed = 0;
            this.stats_rejected = 0;
            this.stats_prevalidated = 0;
        }

        public override bool Parse(String archive, String filename, bool isFirstFile, bool isLastFile, int fileID, int fileCount)
        {
            if (!base.Parse(archive, filename, isFirstFile, isLastFile, fileID, fileCount))
            {
                Log.GetInstance().Error("File '{0}' has been skipped.");
                return false;
            }
            if (isFirstFile)
                this.OnInit();
            this.OnStart();
            bool ret = this.ProcessFile(archive, filename);
            this.OnFinish();
            Log.GetInstance().WriteProgressBar(this.GetStats());
            Log.GetInstance().Debug(this.GetStats());
            this.TriggerWrite(null, false);
            if (isLastFile)
                this.OnFinalize();
            return ret;
        }

        private bool FireAllRules(E entry)
        {
            if(entry.Type == AnEntryType.Regular)
                this.stats_processed++;
            FileEntry.CurrentEntry = entry;
            AProcessor<E>.manager.AssertObject(entry);
            AProcessor<E>.manager.AssertContext(ProcessorContext.GetInstance());
            AProcessor<E>.manager.FireAllRules();
            if (ValidationPool.GetInstance().Entries.Count > 0)
            {
                foreach (FileEntry e in ValidationPool.GetInstance().Entries)
                {
                    this.OnValidatedItem((E)e);
                    this.TriggerWrite((E)e, true);
                    this.entries.Add(e);

                    Log.GetInstance().Trace("Line #" + e.Line + " validated");
                    this.stats_validated++;
                }
                ValidationPool.GetInstance().Entries.Clear();
            }
            else
            {
                Log.GetInstance().Trace("Line #" + this.stats_read + " invalidated");
                this.stats_rejected++;
            }

            if (this.stats_processed % 100 == 0)
                Log.GetInstance().WriteProgressBar(this.GetStats());

            return entry.Validated;
        }

        private String GetStats()
        {
            return String.Format("Statistics: {0} read / {1} discarded / {2} processed / {3} rejected / {4} prevalidated / {5} validated ({6}%)", this.stats_read, this.stats_discarded, this.stats_processed, this.stats_rejected, this.stats_prevalidated, this.stats_validated, (this.stats_bytetoread > 0 ? "" + (this.stats_byteread * 100 / this.stats_bytetoread) : "?"));
        }

        private bool OnInit()
        {
            E entry = new E() { Type = AnEntryType.Special };
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnInit);
            return this.FireAllRules(entry);
        }

        private bool OnStart()
        {
            E entry = new E() { Type = AnEntryType.Special };
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnStart);
            return this.FireAllRules(entry);
        }

        private bool OnFinish()
        {
            E entry = new E() { Type = AnEntryType.Special };
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnFinish);
            return this.FireAllRules(entry);
        }

        private bool OnFinalize()
        {
            E entry = new E() { Type = AnEntryType.Special };
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnFinalize);
            return this.FireAllRules(entry);
        }

        protected bool OnNewItem(E entry)
        {
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnNewItem);
            return this.FireAllRules(entry);
        }

        protected bool OnNewDiscardedItem(E entry)
        {
            if (!this.processDiscardedEntries)
                return false;
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnNewDiscardedItem);
            return this.FireAllRules(entry);
        }

        protected void OnValidatedItem(E entry)
        {
            ProcessorContext.GetInstance().SetEvent(ContextEvent.OnValidatedItem);

            FileEntry.CurrentEntry = entry;
            AProcessor<E>.manager.AssertObject(entry);
            AProcessor<E>.manager.AssertContext(ProcessorContext.GetInstance());
            AProcessor<E>.manager.FireAllRules();
        }

        private void WriteEntry(FileEntry ae)
        {
            AWriter current = null;
            foreach (long outputfile in ae.OutputFiles)
            {
                if (! this.writerPool.ContainsKey(outputfile))
                {
                    ShortFileContext sfc = ProcessorContext.GetInstance().AvailableShortOutputs.Get<ShortFileContext>(outputfile);
                    FS.ManageWorkingDir(sfc.Name);
                    if(sfc.Format != null)
                        current = OutputFactory.Create(sfc.Format);
                    else
                        current = OutputFactory.CreateFromFileName(sfc.Name);
                    if (current == null)
                    {
                        Log.GetInstance().Error("No output has been selected");
                        continue;
                    }
                    current.SetContext(sfc);
                    current.Load(sfc.Name);
                    this.writerPool.Add(outputfile, current);
                }
                current = this.writerPool[outputfile];

                current.WriteEntry(ae);
            }
        }

        public virtual void TriggerWrite(E e, bool partial)
        {
            try
            {
                if (partial)
                {
                    if (this.entries.Count < this.outputBuffer)
                        return;
                    Log.GetInstance().Debug("Writing {0} entr{1}", this.outputBufferTrigger, (this.outputBufferTrigger > 1 ? "ies" : "y"));
                    FileEntry[] writeBuffer = new FileEntry[this.outputBufferTrigger];
                    this.entries.CopyTo(0, writeBuffer, 0, this.outputBufferTrigger);
                    this.entries.RemoveRange(0, this.outputBufferTrigger);
                    foreach (FileEntry ae in writeBuffer)
                    {
                        if(ae.Validated)
                            this.WriteEntry(ae);
                    }
                    writeBuffer = null;
                }
                else
                {
                    Log.GetInstance().Debug("Writing remaining {0} entr{1}", this.entries.Count, (this.entries.Count > 1 ? "ies" : "y"));
                    foreach (FileEntry ae in this.entries)
                    {
                        if (ae.Validated)
                            this.WriteEntry(ae);
                    }
                    foreach (KeyValuePair<long, AWriter> writerFile in this.writerPool)
                    {
                        writerFile.Value.WriteFooter();
                        Log.GetInstance().Debug("File '{0}' has been written", writerFile.Value.Filename);
                    }
                    this.writerPool.Clear();
                }
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            catch (Exception ex)
            {
                Log.GetInstance().Error(ex.ToString());
            }
            finally
            {
                Log.GetInstance().WriteProgressBarEnd();
            }
        }

    }
}
